home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Collection of Tools & Utilities
/
Collection of Tools and Utilities.iso
/
ada
/
gwuada_9.zip
/
TASKING.C
< prev
next >
Wrap
C/C++ Source or Header
|
1993-08-29
|
78KB
|
2,363 lines
/*
* Copyright (C) 1985-1992 New York University
*
* This file is part of the Ada/Ed-C system. See the Ada/Ed README file for
* warranty (none) and distribution info and also the GNU General Public
* License for more details.
*/
/*
+---------------------------------------------------+
| |
| I N T E R P T A S K I N G |
| |
| (C Version) |
| |
| Adapted From Low Level SETL version written by |
| |
| Monte Zweben |
| Philippe Kruchten |
| Jean-Pierre Rosen |
| |
| Original High Level SETL version written by |
| |
| Robert B. K. Dewar |
| Clint Goss |
| Tracey M. Siesser |
| Bernard D. Banner |
| Stephen C. Bryant |
| Gerry Fisher |
| |
| C version written by |
| |
| Robert B. K. Dewar |
| |
+---------------------------------------------------+
*/
/* Include standard header modules */
#include <stdlib.h>
#include <stdio.h>
#include "config.h"
#include "ipar.h"
#include "ivars.h"
#include "int.h"
#include "tasking.h"
#include "segment.h"
#include "iparp.h"
#include "intap.h"
#include "intbp.h"
#include "intcp.h"
#include "miscp.h"
#include "taskingp.h"
#ifdef IBM_PC
#include <time.h>
static void sleep(unsigned);
#endif
#ifdef MONITOR
extern int type_of_delay;
extern long Run_Total_Time;
extern int seconds_per_tick;
extern int step_rate;
#endif
static void done_creation();
static void create_task(int, int, struct rts_item *, int);
static void done_activation();
static void activate_self(int, int);
static void kill(int);
static void abortme();
static void post_abort_one();
static void post_complete_task_one();
static void post_complete_block();
static void post_complete_block_two();
static void task_term();
static void free_task_space();
static void check_termination(int, int);
static void check_done(int, int);
static void check_unterm();
static void tell_termination(int);
static void uncreate_tasks(int);
static void my_set_timer(struct io_item_type *, long);
static int long my_reset_timer(long);
static void wait(long, int);
static void post_wait();
static void catcher(long);
static struct io_item_type *chain(int, long);
static void check_free(struct io_item_type *);
static int get_io(struct io_item_type *);
static void disable_io(struct io_item_type **);
static int remove_io(struct io_item_type **);
static void schedule(int);
static void finish_action(struct rts_item *);
static void make_ready(int, int);
static void transfer(int);
static void evaluate_guards(int [], int);
static void close_guards(int [], int);
static void post_selective_wait(int, int, int [], int []);
static void accept_rdv(int, int, int);
static void post_entry_call();
static void einit(struct entry_type *);
static int eremove(int);
static void eput(struct entry_type *, int, struct q_item **);
static int eget(struct entry_type *);
static void add_lock(struct lock_type *);
static void add_unlock(struct lock_type *);
static void del_lock(struct lock_type *);
static void del_unlock(struct lock_type *);
static int entry_number(int, int, int);
static void multisignal(int, int);
static void add_serviced(int, int);
static int remove_serviced();
static void qinit();
static void enqueue(int, int);
static void enqueue_item(int, struct rts_item *);
static struct rts_item *dequeue(struct ready_q *, int *);
static void context_switch();
/* Global variables for tasking management */
/* id of the owner of the corr. stack */
static int original_task[MAX_TASKS];
/* Head of task chained waiting on clock */
static struct io_item_type *clock_head;
/* Highest priority of tasks to schedule */
static int highest_priority;
/* Previous value of time */
static long last_time;
/* temporary decl of ready queues */
static struct ready_q *ready_queue[MAX_PRIO];
/*-------------------------------------------------------------------------*
* ---- T A S K I N G S Y S T E M --- *
* *
* The module contains all of the tasking control routines. It is *
* divided into nine major sections. Itemized under each section are the *
* routines which are callable from outside the tasking module. Unless *
* interface changes are made it is essential that the status of the *
* stack, on entry and exit from these routines, be maintained. *
* *
* - Task Initialization *
* * initialize_tasking *
* - Task Creation *
* * start_creation *
* - Task Activation *
* * start_activation *
* * union_tasks_declared *
* * end_activation *
* - Task Abortion *
* * abort *
* - Task Termination *
* * complete_task *
* * complete_block *
* * terminate_unactivated *
* * purge_rdv *
* - Timer Maintainence *
* * delay_stmt *
* * clock_interrupt *
* - Task Scheduler *
* * round_robin *
* - Rendezvous *
* * entry_call *
* * selective_wait *
* * end_rendezvous *
* - Utility Routines (queue routines etc. *
* * raise_in_caller *
* * is_callable *
* * is_terminated *
* * count *
* *
*-------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*/
/* T A S K I N G I N I T I A L I Z A T I O N */
/* */
/* Procedure to perform necessary initialization for tasking system */
/*-------------------------------------------------------------------------*/
void initialize_tasking() /*;initialize_tasking*/
{
int i;
int *p;
long itime();
/* Initialize variables */
last_task = 1;
for (i = 0; i < MAX_TASKS; i++) {
STACK(i) = (int *)0;
ORIG(i) = NULL_TASK;
}
qinit();
time_offset = 0;
last_time = itime();
clock_head = NULL;
next_clock = itime();
next_clock_flag = FALSE;
rr_counter = 0;
/* Initiate the idle task */
p = STACK(0) = (int *) malloc((unsigned) sizeof(int) * main_task_size);
ORIG(0) = 0;
if (p == (int *)0) {
printf("Unable to allocate stack\n");
exit(RC_ABORT);
}
TCB_ABNORMAL(0) = 0;
TCB_ACTION(0) = NO_ACTION;
TCB_BROTHER(0) = 0;
TCB_BLOCK_PTR(0) = (int *) 0;
TCB_CURR_ENTRY(0) = NULL;
TCB_EVENT(0) = NO_EVENT;
TCB_EXCEPTION(0) = 0;
TCB_ENTRY_ITEM(0) = 0;
TCB_FIRST(0) = 0;
TCB_ID(0) = 0;
TCB_IO_ITEM(0) = NULL;
TCB_MASTER_TASK(0) = NULL_TASK;
TCB_MASTER_BLOCK(0) = 0;
TCB_NEXT(0) = NULL_TASK;
TCB_NUM_ITEMS(0) = 0;
TCB_NUM_NOTERM(0) = 0;
TCB_NUM_DEPS(0) = 0;
TCB_NUM_ENTRIES(0) = 0;
TCB_NUM_EVENTS(0) = 0;
TCB_PARENT(0) = NULL_TASK;
TCB_PRIO(0) = 0;
TCB_RDV(0) = 0;
TCB_RTS_ITEM(0) = NULL;
TCB_SAVE_PRIO(0) = 0;
TCB_SERVICED(0) = 0;
TCB_STATUS(0) = ACTIVE;
TCB_TBASE(0) = 1;
TCB_TOFF(0) = 52;
TCB_WHAT(0) = 0;
TCB_WHO(0) = NULL_TASK;
/* Set context for idle task */
STACKPTR(0) = WORDS_TCB;
tp = 0;
cur_stack = STACK(tp);
cur_stackptr = STACKPTR(tp);
bfp = 0;
ip = TASK_CODE_OFFSET;
cs = 1;
sfp = 0;
lin = 0;
exr = 0;
cur_code = code_segments[cs];
}
/*--------------------------------------------------------------------------*/
/* T A S K C R E A T I O N */
/* */
/* The following set of routines perform task creation */
/*--------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*/
/* START CREATION */
/* Create an array of tasks, do so by creating a RTS item for the array */
/* As tasks become ready to run they will perform activations etc. first */
/* using this RTS item */
/*-------------------------------------------------------------------------*/
void start_creation(int templ_base, int templ_off) /*;start_creation*/
{
int mult = 1;
struct rts_item *rts;
if (BLOCK_FRAME->bf_tasks_declared == NULL)
push_task_frame(NULL_TASK); /* create null frame */
rts = (struct rts_item *) malloc(sizeof(struct rts_item));
if (rts == (struct rts_item *)0) {
raise(STORAGE_ERROR, "Allocating space for task");
return;
}
rts->tcbs = (int *) malloc((unsigned) sizeof(int)*mult);
if (rts->tcbs == (int *)0) {
raise(STORAGE_ERROR, "Allocating space for task");
return;
}
RTS_TYPE(rts) = CREATE;
RTS_PRIO(rts) = MY_PRIO;
RTS_TEMPL_BASE(rts) = templ_base;
RTS_TEMPL_OFF(rts) = templ_off;
RTS_MULT(rts) = mult;
RTS_NEXT(rts) = NULL;
RTS_PARENT(rts) = tp;
MY_WHAT = NULL_TASK;
MY_NUM_ITEMS = mult;
enqueue_item(MY_PRIO, rts);
schedule(DONE_CREATION);
}
/*--------------------------------------------------------------------------*/
/* DONE CREATION */
/* Performed after all of my tasks have finished creation. Add current */
/* RTS item to chain */
/*--------------------------------------------------------------------------*/
static void done_creation() /*;done_creation*/
{
if (MY_EXCEPTION == STORAGE_ERROR) {
raise(STORAGE_ERROR, "Not enough space for new tasks");
MY_EXCEPTION = 0;
}
if (MY_WHAT != NULL_TASK) /* MY_WHAT is leader of RTS */
TCB_BROTHER(MY_WHAT) = FAS(MY_TASKS_DECLARED, MY_WHAT);
PUSH(MY_WHAT); /* Must add item to chain of rts items */
}
/*-----------------------------------------------------------------------*/
/* CREATE TASK */
/* Procedure to create task and put it on parents frame chain */
/*-----------------------------------------------------------------------*/
static void create_task(int templ_base, int templ_off, struct rts_item *rts,
int my_id) /*;create_task*/
{
int task_nr, parent; /* nr of the task to be created */
int i, *p, old_last_task; /* temporary variable */
/* STEP 1: Search the stack_segments for a stack */
old_last_task = last_task;
task_nr = INC(last_task);
parent = RTS_PARENT(rts);
for(;task_nr < old_last_task + MAX_TASKS; task_nr = INC(last_task))
if (ORIG(task_nr) == NULL_TASK) break;
if (task_nr >= old_last_task + MAX_TASKS) {/* Error if no avail stack */
FAS(&(TCB_EXCEPTION(parent)),PROGRAM_ERROR);
multisignal(parent, NO_EVENT);
return;
}
/* STEP 2: Create task frame for new task */
p=STACK(task_nr) = (int *) malloc((unsigned) sizeof(int)*new_task_size);
ORIG(task_nr) = task_nr;
/* STEP 3 : Create storage area for task and initialize */
if (p == (int *)0) {
RTS_TCBS(rts, my_id) = NULL_TASK;
DEC(RTS_MULT(rts));
FAS(&(TCB_EXCEPTION(parent)), STORAGE_ERROR);
STACKPTR(task_nr) = 0;
multisignal(parent, NO_EVENT);
}
else {
TCB_ABNORMAL(task_nr) = 0;
TCB_ACTION(task_nr) = ACTIVATE_SELF;
TCB_BLOCK_PTR(task_nr) = (int *) 0;
TCB_BROTHER(task_nr) = NULL_BROTHER;
TCB_CURR_ENTRY(task_nr) = NULL;
TCB_ENTRY_ITEM(task_nr) = NULL;
TCB_EVENT(task_nr) = NO_EVENT;
TCB_EXCEPTION(task_nr) = 0;
TCB_FIRST(task_nr) = 0;
TCB_ID(task_nr) = my_id;
TCB_IO_ITEM(task_nr) = NULL;
TCB_MASTER_BLOCK(task_nr) = 0;
TCB_MASTER_TASK(task_nr) = NULL_TASK;
TCB_NEXT(task_nr) = NULL_TASK;
TCB_NUM_ITEMS(task_nr) = 0;
TCB_NUM_DEPS(task_nr) = 0;
TCB_NUM_ENTRIES(task_nr) =
TASK(ADDR(templ_base,templ_off))->nb_entries;
TCB_NUM_EVENTS(task_nr) = 0;
TCB_NUM_NOTERM(task_nr) = 0;
TCB_PARENT(task_nr) = RTS_PARENT(rts);
TCB_PRIO(task_nr) = TASK(ADDR(templ_base, templ_off))->priority;
TCB_RDV(task_nr) = 0;
TCB_RTS_ITEM(task_nr) = rts;
TCB_SAVE_PRIO(task_nr) = 0;
TCB_SERVICED(task_nr) = NULL_TASK;
TCB_STATUS(task_nr) = ACTIVATING;
TCB_TBASE(task_nr) = templ_base;
TCB_TOFF(task_nr) = templ_off + WORDS_TASK;
TCB_WHAT(task_nr) = 0;
TCB_WHO(task_nr) = NULL_TASK;
/* add space for queue headers */
for (i=1; i<=TASK(ADDR(templ_base,templ_off))->nb_entries; i++)
einit(TCB_ENTRY(task_nr, i));
p = (int *)(TCB_ENTRY(task_nr, i));
*p++ = 0; /* EXR */
*p++ = 0; /* LIN */
*p++ = 0; /* SFP */
*p++ = 0; /* CS */
*p++ = 0; /* IP */
STACKPTR(task_nr) = p - STACK(task_nr);
*p++ = 0; /* BFP */
#ifdef GWUMON
CWK_CHANGE_TASK_STAT(task_nr, 'C',
TASK(ADDR(templ_base,templ_off))->task_name,
TASK(ADDR(templ_base,templ_off))->task_file);
#endif
#ifdef TRACE
if(tasking_trace)
printf("task %d creating task %d\n",tp,task_nr);
#endif
RTS_TCBS(TCB_RTS_ITEM(task_nr), TCB_ID(task_nr)) = task_nr;
TCB_WHAT(TCB_PARENT(task_nr)) = task_nr;
multisignal(RTS_PARENT(TCB_RTS_ITEM(task_nr)), NO_EVENT);
}
}
/*--------------------------------------------------------------------------*
* T A S K A C T I V A T I O N *
* *
* The following set of routines perform task activation *
*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*
* START ACTIVATION *
* This routines is used by a task to preprocess it's task list in order *
* to find out how many tasks must be activated. It then adds the items *
* in the tasks list to the ready queue in order that they may then be *
* activated in parallel. When they are done being activated, the parent *
* task may continue processing. *
*--------------------------------------------------------------------------*/
void start_activation(int task_list, int task_master, int task_block)
/*;start_activation*/
{
int i, task, next_task, num_tasks;
struct rts_item *item;
if (MY_ABNORMAL) {
uncreate_tasks(task_list);
abortme();
return;
}
task = task_list;
num_tasks = 0;
while (task != NULL_TASK) {
num_tasks += RTS_MULT(TCB_RTS_ITEM(task));
task = TCB_BROTHER(task);
}
MY_NUM_ITEMS = num_tasks;
MY_EVENT = NO_EVENT;
if (TCB_ABNORMAL(task_master)) return;
MY_STATUS = ACTIVATING;
task = task_list;
for (i=0;i<num_tasks;i++) {
next_task = TCB_BROTHER(task);
item = TCB_RTS_ITEM(task);
RTS_MASTER_TASK(item) = task_master;
RTS_MASTER_BLOCK(item) = task_block;
RTS_PRIO(item) = MY_PRIO;
RTS_TYPE(item) = ACTIVATE;
RTS_NEXT(item) = NULL;
enqueue_item(MY_PRIO, item);
task = next_task;
}
schedule(DONE_ACTIVATION);
}
/*--------------------------------------------------------------------------*/
/* DONE ACTIVATION */
/* This routines is called by the parent task when all of its tasks have */
/* finished their activation. It checks that it is ok, if not it aborts */
/* all of the children. */
/*--------------------------------------------------------------------------*/
static void done_activation() /*;done_activation*/
{
if (MY_ABNORMAL) {
abortme();
return;
}
if (MY_EVENT == TASKERR_EVENT) {
raise(TASKING_ERROR, "Tasking error in activation");
MY_EXCEPTION = 0;
}
else if (MY_EVENT == PROGERR_EVENT) {
raise(PROGRAM_ERROR, "Activating an unelaborated task");
MY_EXCEPTION = 0;
}
MY_ACTION = NO_ACTION;
}
int union_tasks_declared(int list1, int list2) /*;union_tasks_declared */
{
int head2, tail1;
if (list1 == NULL_TASK) return list2;
if (list2 == NULL_TASK) return list1;
tail1 = list1;
while (TCB_BROTHER(tail1) != NULL_TASK) tail1 = TCB_BROTHER(tail1);
head2 = FAS(&(list2), list1);
FAS(&(TCB_BROTHER(tail1)), head2);
return list1;
}
/*------------------------------------------------------------------------*/
/* TASK ACTIVATION */
/* Procedure to activate self and put on the bf_subtasks chain if leader */
/*------------------------------------------------------------------------*/
static void activate_self(int task_master, int task_bfp) /*;activate_self*/
{
int *ptr; /* memory address */
int i, val1, val2, tmp_base, tmp_off; /* temporary base */
if (MY_BROTHER != NULL_BROTHER) /* I am leader of RTS */
MY_BROTHER =
FAS(&(((struct bf *)(STACK(task_master)+task_bfp))->bf_subtasks), tp);
if (MY_STATUS == TERMINATED || MY_ABNORMAL) { /* may have been aborted */
MY_STATUS = TERMINATED;
multisignal(MY_PARENT, NO_EVENT);
schedule(NO_ACTION);
return;
}
MY_MASTER_TASK = task_master;
MY_MASTER_BLOCK= task_bfp;
MY_NUM_NOTERM = 1; /* myself */
MY_NUM_DEPS = 1; /* myself */
ptr = ADDR(MY_TBASE, MY_TOFF-WORDS_TASK);
tmp_base = TASK(ptr)->body_base;
tmp_off = TASK(ptr)->body_off;
ptr = ADDR(tmp_base, tmp_off);
cs = *ptr;
if (cs < 1) { /* Not elaborated !! TBSL */
TCB_EXCEPTION(MY_PARENT) = PROGRAM_ERROR;
multisignal(MY_PARENT, PROGERR_EVENT);
schedule(NO_ACTION);
return;
}
ip = TASK_CODE_OFFSET;
/* reserve space for local variables */
/* note: this could be somehow optimized by just moving */
/* the Top of Stack.... */
#ifdef ALIGN_WORD
val1 = get_int((int *)(code_segments[cs] + code_seglen[cs] - sizeof(int)
-1));
#else
val1 = *(int *)(code_segments[cs] + code_seglen[cs] - sizeof(int) - 1);
#endif
for (i = 0; i < val1; i++)
PUSH(0);
i = cur_stackptr + NB_REGISTERS - 1;
PUSH(i); /* SFP -- this is a trap */
PUSH(cs); /* CS */
PUSH(lin); /* LIN*/
PUSH(ip); /* IP */
sfp = cur_stackptr + 1;
/* copy relay set */
val2 = *++ptr * 2; /* length */
for (i = 0; i < val2; i++)
PUSH(*++ptr);
/* Dummy block frame for trapping exceptions */
PUSH(0); /* bfp */
bfp = cur_stackptr;
PUSHP(0L); /* data_link */
PUSHP(0L); /* tasks_declared */
PUSH(1); /* num_noterm */
PUSH(1); /* num_deps */
PUSH(NULL_TASK);/* subtasks */
PUSH(2); /* exception vector */
cur_code = code_segments[cs];
/* This must occur after possible to detect all errors */
INC(TCB_NUM_NOTERM(MY_MASTER_TASK));
INC(TCB_NUM_DEPS(MY_MASTER_TASK));
INC(BF_NUM_NOTERM(MY_MASTER_TASK, MY_MASTER_BLOCK));
INC(BF_NUM_DEPS(MY_MASTER_TASK, MY_MASTER_BLOCK));
#ifdef GWUMON
CWK_CHANGE_TASK_STAT(tp, 'A', "", "" );
#endif
#ifdef TRACE
if (tasking_trace) {
printf("task %d activating task %d\n",task_master,tp);
}
#endif
}
/*-------------------------------------------------------------------------*/
/* END ACTIVATION */
/* Procedure called at the end of activation to signal the parent task */
/* that everything is OK (or not as the case may be) */
/*-------------------------------------------------------------------------*/
void end_activation(int term_code) /*;end_activation*/
{
/* after we passed the end of activation, the exception vector */
/* of the very first block shall designate the task_trap. */
BF_HANDLER(tp, MY_PREVIOUS_BFP) = 2;
if (term_code == 0) {
TCB_EXCEPTION(MY_PARENT) = TASKING_ERROR;
DEC(RTS_MULT(MY_RTS_ITEM));
RTS_TCBS(MY_RTS_ITEM, MY_ID) = NULL_TASK;
multisignal(MY_PARENT, TASKERR_EVENT);
}
else {
MY_STATUS = ACTIVE;
multisignal(MY_PARENT, NO_EVENT);
}
}
/*--------------------------------------------------------------------------*/
/* A B O R T R O U T I N E S */
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
/* ABORT */
/* Procedure used to abort nr_task tasks whose numbers are on the stack */
/*--------------------------------------------------------------------------*/
void abort(int nr_tasks) /*;abort*/
{
int current; /* pointer to the task currently aborting */
int i; /* temporary loop index */
for (i = 0; i < nr_tasks; i++) {
POP(current);
kill(current);
}
if (MY_ABNORMAL) /* Suicide! */
abortme();
}
/*--------------------------------------------------------------------------*/
/* KILL */
/* Procedure to abort task, after having aborted its dependent tasks. If */
/* nobody in the family is engaged in a rendezvous, then the task is made */
/* terminated and space of dependent tasks is released. This procedure does */
/* not call WAIT: this has to be done by the caller if it aborts itself. */
/*--------------------------------------------------------------------------*/
static void kill(int task) /*;kill*/
{
int task2, dep, i, j, block;
/* STEP 1: Check status etc. of task being aborted ... */
if (INC(TCB_ABNORMAL(task)) || (TCB_STATUS(task) == TERMINATED)) {
/* more than one task trying to abort it, must clean up ... */
#ifdef GWUMON
CWK_CHANGE_TASK_STAT(task, 'E', "", "" );
#endif
#ifdef TRACE
if (tasking_trace)
printf("task %d aborting terminated task %d\n",tp,task);
#endif
DEC(TCB_ABNORMAL(task)); /* avoid overflow */
return;
}
#ifdef GWUMON
CWK_CHANGE_TASK_STAT(task, 'E', "", "" );
#endif
#ifdef TRACE
if (tasking_trace && TCB_ABNORMAL(task))
printf("task %d aborting task %d\n",tp,task);
#endif
/* STEP 2: Kill all dependent tasks by block
* I can spawn tasks and use a multi-wait count here to make sure done
*/
if (TCB_BLOCK_PTR(task) == NULL) block = 0; /* no yet activated ... */
else block = *(TCB_BLOCK_PTR(task));
while (block != 0) {
task2 = BF_SUBTASKS(task, block);
while (task2 != NULL_TASK) {
i = 0;
j = 0;
while (i < RTS_MULT(TCB_RTS_ITEM(task2)))
if ((dep=RTS_TCBS(TCB_RTS_ITEM(task2),j++)) != NULL_TASK) {
i++;
kill(dep);
}
task2 = TCB_BROTHER(task2);
}
block = BF_PREVIOUS_BFP(task, block);
}
/* STEP 3: Perform cleanup. Try to disable task if it waiting, if
* successful then must signal. Otherwise it will discover itself
* when awakens or when it reaches a synchronization point (when it
* is 'active' but paused by the round-robin scheduling scheme).
*/
switch (TCB_STATUS(task)) {
case COMPLETED :
case ACTIVATING :
case ACTIVE :
case COMPLETE_BLOCK :
break;
case SELECTING_TERM:
case SELECTING_NOTERM:
if (FAS(&(TCB_RDV(task)),0) == 1)
make_ready(task, ABORT_EVENT);
break;
case TIMED_RDV:
case CALLING_RDV:
if (eremove(task)) make_ready(task, ABORT_EVENT);
break;
case WAIT:
if (remove_io(&(TCB_IO_ITEM(task))))
make_ready(task, ABORT_EVENT);
break;
default:
raise(SYSTEM_ERROR, "Aborting task in unknown state");
break;
} /* status TERMINATED & ABNORMAL already tested in kill.
* QUIESCENT depends on ABNORMAL (Cf post_selective_wait & abortme)
* COMPLETE_BLOCK and ACTIVE have been added (Cf round-robin)
*/
}
/*--------------------------------------------------------------------------*/
/* ABORTME */
/* Task has discovered it was aborted. kids in select term have been sig- */
/* nalled. Completed kids will be notified when their kids are through. */
/* My terminated kids are already done. */
/*--------------------------------------------------------------------------*/
static void abortme() /*;abortme */
{
purge_rdv(tp);
if ((MY_STATUS != QUIESCENT) && (DEC(MY_NUM_NOTERM) != 1)) {
MY_STATUS = COMPLETED;
schedule(ABORT_ONE);
}
else post_abort_one();
}
static void post_abort_one() /*;post_abort_one*/
{
if (MY_STATUS != QUIESCENT)
check_termination(MY_MASTER_TASK, MY_MASTER_BLOCK);
task_term(); /* not strictly needed for abort, just "term-wave" */
}
/*--------------------------------------------------------------------------*/
/* T E R M I N A T I O N */
/* */
/* The following set of routines maintain the counters and data structures */
/* used to perform task temination. They are divided into two sets: those */
/* used in the termination of blocks and subprograms and those used in the */
/* termination of tasks. */
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
/* COMPLETE TASK */
/* This is the last instruction in a task's instruction stream */
/*--------------------------------------------------------------------------*/
void complete_task() /*;complete_task*/
{
MY_STATUS = COMPLETED;
if (DEC(MY_NUM_NOTERM) != 1) schedule(COMPLETE_TASK_ONE);
else post_complete_task_one();
}
static void post_complete_task_one() /*;post_complete_task_one*/
{
check_termination(MY_MASTER_TASK, MY_MASTER_BLOCK);
if (INC(MY_ABNORMAL) == 0)
task_term();
else if (DEC(MY_NUM_DEPS) != 1) schedule(FREE_TASK_SPACE);
else free_task_space();
}
/*--------------------------------------------------------------------------*/
/* COMPLETE BLOCK */
/*--------------------------------------------------------------------------*/
void complete_block() /*;complete_block*/
{
MY_STATUS = COMPLETE_BLOCK;
if (DEC(MY_BF_NUM_NOTERM) != 1)
schedule(COMPLETE_BLOCK_ONE);
else post_complete_block();
}
static void post_complete_block() /*;post_complete_block*/
{
if (MY_ABNORMAL == 0)
tell_termination(*MY_BLOCK_PTR);
if (DEC(MY_BF_NUM_DEPS) != 1) schedule(COMPLETE_BLOCK_TWO);
else post_complete_block_two();
}
static void post_complete_block_two() /*;post_complete_block_two*/
{
MY_STATUS = ACTIVE;
}
/*--------------------------------------------------------------------------*/
/* TERMINATION SUPPORT ROUTINES */
/*--------------------------------------------------------------------------*/
static void task_term() /*;task_term*/
{
int block;
if (DEC(MY_NUM_DEPS) != 1) {
block = *MY_BLOCK_PTR;
while (block != 0) {
tell_termination(block);
block = BF_PREVIOUS_BFP(tp, block);
}
schedule(FREE_TASK_SPACE);
}
else free_task_space();
}
static void free_task_space() /*;free_task_space */
{
int block;
block = *MY_BLOCK_PTR;
while (block != 0) {
uncreate_tasks(BF_SUBTASKS(tp, block));
block = BF_PREVIOUS_BFP(tp, block);
}
MY_STATUS = TERMINATED;
check_done(MY_MASTER_TASK, MY_MASTER_BLOCK);
schedule(NO_ACTION);
}
/* Recursive procedure to check master tasks and blocks called by quiescent */
/* dependent of "task" and "block" */
static void check_termination(int task, int block) /*;check_termination */
{
if ((task != NULL_TASK) && (task != 0) && (DEC(TCB_NUM_NOTERM(task)) == 1))
if ((TCB_STATUS(task) == COMPLETED) && (INC(TCB_FIRST(task)) == 0)) {
if (TCB_NUM_NOTERM(task) == 0)
make_ready(task, TERMINATE_EVENT);
}
else
check_termination(TCB_MASTER_TASK(task),TCB_MASTER_BLOCK(task));
if ((block != 0) && (DEC(BF_NUM_NOTERM(task, block)) == 1))
make_ready(task, TERMINATE_EVENT);
}
static void check_done(int task, int block) /*;check_done*/
{
int dd;
dd = DEC(TCB_NUM_DEPS(task));
if ((DEC(BF_NUM_DEPS(task, block)) == 1) || (dd == 1))
make_ready(task, NO_EVENT);
}
static void check_unterm() /*;check_unterm*/
{
if (INC(MY_NUM_NOTERM) == 0) {
INC(TCB_NUM_NOTERM(MY_MASTER_TASK));
INC(BF_NUM_NOTERM(MY_MASTER_TASK, MY_MASTER_BLOCK));
}
}
static void tell_termination(int block) /*;tell_termination*/
{
int i, j, dep, task;
task = BF_SUBTASKS(tp, block);
while (task != NULL_TASK) {
i = 0;
j = 0;
while (i < RTS_MULT(TCB_RTS_ITEM(task)))
if ((dep=RTS_TCBS(TCB_RTS_ITEM(task),j++)) != NULL_TASK) {
i++;
if ((TCB_STATUS(dep) != TERMINATED)
&& (INC(TCB_ABNORMAL(dep)) == 0))
make_ready(dep, TERMINATE_EVENT);
}
task = TCB_BROTHER(task);
}
}
/* Procedure used when an exception is raised to terminate tasks that have
* created, but not yet activated.(LRM 9.3(4)) Such tasks may have pending
* rendezvous. All tasks linked to task frames depending on the current BFP
* are made terminated. They are linked together and put on bf_subtasks in
* order to release their space when the block is exited.
*/
void terminate_unactivated() /*;terminate_unactivated */
{
int current, next, removed, dep, i, j;
int *task_frame;
task_frame = MY_TASKS_DECLARED;
if (task_frame != (int *)0) {
removed = NULL_TASK;
*(task_frame - WORDS_PTR - 1) = -(*(task_frame - WORDS_PTR - 1));
for (;;) {
current = *task_frame;
if (current != NULL_TASK) {
for (;;) {
i = 0;
j = 0;
while (i < RTS_MULT(TCB_RTS_ITEM(current)))
if ((dep=RTS_TCBS(TCB_RTS_ITEM(current),j++))
!= NULL_TASK) {
i++;
TCB_STATUS(dep) = TERMINATED;
purge_rdv(dep);
}
next = TCB_BROTHER(current);
if (next == NULL_TASK)
break;
current = next;
}
/* we are still in the context of the last task on the chain
* merge the chain with previous one
*/
TCB_BROTHER(current) = removed;
removed = *task_frame;/* head of the chain */
}
task_frame = *((int **)(task_frame - WORDS_PTR));
if (task_frame == (int *)0)
break;
}
if (removed != NULL_TASK) /* merge on bf_subtasks to release space */
MY_SUBTASKS = union_tasks_declared(removed, MY_SUBTASKS);
MY_TASKS_DECLARED = (int *)0;
}
}
/* Procedure to raise TASKING_ERROR in all tasks waiting for or engaged */
/* in a rendezvous with the currently active task */
void purge_rdv(int curr) /*;purge_rdv */
{
int task, i;
for (i = 1; i <= TCB_NUM_ENTRIES(curr); i++)
while (ENTRY_COUNT(TCB_ENTRY(curr,i)) > 0) {
DEC(ENTRY_COUNT(TCB_ENTRY(curr,i)));
if ((task = eget(TCB_ENTRY(curr,i))) != NULL_TASK) {
TCB_EXCEPTION(task) = TASKING_ERROR;
make_ready(task, TASKERR_EVENT);
}
}
task = TCB_SERVICED(curr);
while (task != NULL_TASK) {
TCB_EXCEPTION(task) = TASKING_ERROR;
make_ready(task, TASKERR_EVENT);
task = TCB_NEXT(task);
}
}
static void uncreate_tasks(int task_list) /*;uncreate_tasks */
{
int task, next, i, j;
struct rts_item *item;
task = task_list;
while (task > 0) {
next = TCB_BROTHER(task);
item = TCB_RTS_ITEM(task);
i = 0;
j = 0;
while (i < RTS_MULT(item))
if ((task = RTS_TCBS(item,j++)) != NULL_TASK) {
i++;
efree((char *) STACK(task));
STACK(task) = (int *) 0;
ORIG(task) = NULL_TASK;
}
task = next;
}
}
/*--------------------------------------------------------------------------*/
/* T I M E M A N A G E M E N T */
/* */
/* The following set of routines perform time management for "delay" */
/* statements. The chain of waiting tasks associated with each PE */
/* is sorted in ascending time of end of delay. The time associated */
/* with each entry in the chain in the amount of time it must wait after */
/* the previous entry is done (e.g. the wait time is kept incrementally) */
/* A global variable "next_clock" keeps the absolute time value of the next*/
/* interrupt. When the interrupt processing routine is called all interupt*/
/* which are now ready -or- were previously ready are made ready. */
/* Io_items are freed by either the owning task or the interrupt */
/* process. If the io_item has been disabled, it is freed by the catcher */
/* routine. Otherwise it is freed by the process when it awakens from */
/* make ready. */
/*--------------------------------------------------------------------------*/
static void my_set_timer(struct io_item_type *item, long now) /*;my_set_timer*/
{
next_clock = II_DELTA(item) + now;
}
static int long my_reset_timer(long now) /*;my_reset_timer*/
{
last_time = now;
return(next_clock - now);
}
/*--------------------------------------------------------------------------*/
/* DELAY_STMT */
/* Guarantees that all wait times are positive (provided for compatability)*/
/* Note that it recognizes three types of delays: */
/* 0 - signifies simply a request to context switch */
/* ENDLESS - signifies simply relinquishing the CPU */
/* other - actual create a timer chain element and chain it */
/*--------------------------------------------------------------------------*/
void delay_stmt(long delay_time) /*;delay_stmt*/
{
long effective_delay;
effective_delay = delay_time >= 0 ? delay_time : 0;
#ifdef GWUMON
CWK_CHANGE_TASK_STAT(tp, 'D', "", "" );
#endif
#ifdef TRACE
if (rendezvous_trace) {
printf("task %d delaying %ld effective delay %ld \n", tp,
delay_time, effective_delay);
}
#endif
if (effective_delay == (long) ENDLESS)
schedule(NO_ACTION);
else if (effective_delay == 0)
context_switch();
else
wait(effective_delay, WAIT);
}
/*--------------------------------------------------------------------------*/
/* WAIT */
/* Routines which makes the task wait for the delay time */
/*--------------------------------------------------------------------------*/
static void wait(long delay, int action) /*;wait*/
{
MY_STATUS = WAIT;
if (MY_ABNORMAL && MY_STATUS != TERMINATED) {
abortme();
return;
}
MY_IO_ITEM = chain(tp, delay);
schedule(action);
}
static void post_wait() /*;post_wait*/
{
MY_STATUS = ACTIVE;
if (MY_ABNORMAL)
abortme();
}
/*--------------------------------------------------------------------------*/
/* CATCHER */
/* Routine called when a timer interrupt occurs. */
/*--------------------------------------------------------------------------*/
void clock_interrupt(long now_time) /*;clock_interrupt*/
{
catcher(now_time);
context_switch();
}
static void catcher(long now_time) /*;catcher*/
{
struct io_item_type *item;
long time;
/* Time Out head of io_item list and all others following with delta 0 */
time = now_time - last_time;
last_time = now_time;
while ((clock_head != NULL) && (II_DELTA(clock_head)<=time)) {
item = II_NEXT(clock_head); /* TO others waiting in interval */
time -= II_DELTA(clock_head);
check_free(clock_head);
clock_head = item;
}
/* remove interior deletes from head of list */
item = clock_head;
while ((clock_head != NULL) && (II_FLAG(clock_head) == 0)) {
time -= II_DELTA(clock_head);
item = II_NEXT(clock_head);
efree((char *) clock_head);
clock_head = item;
}
if (clock_head != NULL) {
II_DELTA(clock_head) -= time;
my_set_timer(clock_head, now_time);
}
else next_clock_flag = FALSE;
}
/*--------------------------------------------------------------------------*/
/* CHAIN */
/* Called when execute a delay. Add task to timeout chain */
/*--------------------------------------------------------------------------*/
static struct io_item_type *chain(int task, long delay) /*;chain*/
{
long my_reset_time(), itime();
long now;
struct io_item_type *new_item, *item, *prev_item;
now = itime() + time_offset;
if (TCB_IO_ITEM(task) == NULL) {
new_item=(struct io_item_type *)malloc(sizeof(struct io_item_type));
if (new_item == (struct io_item_type *)0) {
raise(STORAGE_ERROR, "Allocating space for timer chain");
return(NULL);
}
}
else new_item = TCB_IO_ITEM(task);
if (clock_head == NULL) {
clock_head = new_item;
II_TASK(new_item) = task;
II_FLAG(new_item) = TCB_STATUS(task);
II_NEXT(new_item) = NULL;
II_DELTA(new_item) = delay;
}
else {
II_DELTA(clock_head) = my_reset_timer(now); /* time in intval*/
II_TASK(new_item) = task;
II_FLAG(new_item) = TCB_STATUS(task);
/* seq. search for new_item's position based on relative deltas */
if (delay <= II_DELTA(clock_head)) {
II_NEXT(new_item) = clock_head;
II_DELTA(new_item) = delay;
II_DELTA(clock_head) -= delay;
clock_head = new_item;
}
else {
item = clock_head;
prev_item = clock_head;
while ((item != NULL) && (delay-II_DELTA(item) > 0)) {
delay -= II_DELTA(item);
prev_item = item;
item = II_NEXT(item);
}
II_NEXT(new_item) = item;
II_NEXT(prev_item) = new_item;
II_DELTA(new_item) = delay;
if (item != NULL)
II_DELTA(item) -= delay;
}
}
next_clock_flag = TRUE;
my_set_timer(clock_head, now);
return(new_item);
}
/*--------------------------------------------------------------------------*/
/* CHECK FREE */
/* Called by catcher to see if timeout is possible */
/*--------------------------------------------------------------------------*/
static void check_free(struct io_item_type *item) /*;check_free*/
{
int value;
value = get_io(item);
if (value > 0) /* anything else being waited for has been disble */
make_ready(II_TASK(item), TIMER_EVENT);
/* Originally we were going to deallocate the io_item. This was later
* changed EXCEPT in the case where it was already disabled ...
*/
else if (value == -1)
efree((char *) item);
}
/*--------------------------------------------------------------------------*/
/* GET IO */
/*--------------------------------------------------------------------------*/
static int get_io(struct io_item_type *item) /*;get_io*/
{
int wake;
switch (FAS(&(II_FLAG(item)),TIMER_EVENT)){ /* was task's status */
case DISABLED_EVENT:
wake = -1;
break; /* already gone */
case TIMED_RDV :
wake = eremove(II_TASK(item));
FAS(&(II_FLAG(item)), 0);
break;
case SELECTING_NOTERM: /* disable */
case SELECTING_TERM:
wake = FAS(&(TCB_RDV(II_TASK(item))),0);
FAS(&(II_FLAG(item)), 0);
break;
case WAIT :
wake = 1;
break; /* must wake up */
case 0 :
wake = -1;
break; /* innner remove*/
default :
raise(SYSTEM_ERROR, "Unexpected event");
break;
}
II_FLAG(item) = 0;
return wake;
}
/*--------------------------------------------------------------------------*/
/* DISABLE IO */
/* Called when rdv ti disable timeouts. No timeouts possible, but avoids */
/* race (that item.flag may be gotten, rendezvous, then RDV reset for next */
/* rendezvous). */
/*--------------------------------------------------------------------------*/
static void disable_io(struct io_item_type **item_ptr) /*;disable_io*/
{
struct io_item_type *item;
item = *item_ptr;
if (item != (struct io_item_type*)0) {
if (FAS(&(II_FLAG(item)),DISABLED_EVENT) == TIMER_EVENT) {
/* Timeout in progress, wait for it to end then delete */
while (II_FLAG(item) != 0) continue;
efree((char *) item);
}
*item_ptr =(struct io_item_type *)0 ;
}
}
/*--------------------------------------------------------------------------*/
/* REMOVE IO */
/* Called when abort in delay */
/*--------------------------------------------------------------------------*/
static int remove_io(struct io_item_type **item_ptr) /*;remove_io */
{
struct io_item_type *item;
item = *item_ptr;
if (item != (struct io_item_type *)0)
if (FAS(&(II_FLAG(item)),DISABLED_EVENT) == TIMER_EVENT) {
efree((char *) item); /* too late, has timed out ... */
*item_ptr = (struct io_item_type *)0;
return 0;
}
return 1;
}
/*--------------------------------------------------------------------------*
* S C H E D U L E R *
* *
* The following set of routines perform decentralized scheduling based *
* on two different schemes: "run-until-blocked" or "round-robin". Because *
* the interpreter simply simulates tasks, it is absolutely necessary that *
* once "schedule" is call and control is passed to a new virtual task, *
* that the interpretor return to the highest level without executing any *
* other code (excepting the cleanup routines called in "schedule"). *
* Thus any statement which (recursively) executes "schedule" must be *
* immediately succeeded by a return statement. The "dangerous" routines *
* in the system are: *
* *
* - activate_self - abort - abortme *
* - complete_task - complete_task_one - task_term *
* - complete_block - post_complete_block *
* - wait - post_wait - delay_stmt *
* - entry_call - post_entry_call - end_rendezvous *
* - selective_wait - post_selective_wait *
*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*
* SCHEDULE *
* Block the currently executing task and select a new task to run. *
*--------------------------------------------------------------------------*/
static void schedule(int action) /*;schedule*/
{
int done, p, i;
struct rts_item *item;
MY_ACTION = action;
if (DEC(MY_NUM_EVENTS) > 0) {
finish_action(MY_RTS_ITEM); /* an event is waiting */
return;
}
if (action == CONTEXT_SWITCH) INC(MY_NUM_EVENTS);
done = FALSE;
while (!done) {
for (p=MAX_PRIO-1;p>=0;p--) {
item = dequeue(ready_queue[p], &i);
if (item != (struct rts_item *)0) {
switch(RTS_TYPE(item)) {
case ACTIVATE :
case READY :
#ifdef GWUMON
CWK_CHANGE_TASK_STAT(item->tcbs[i], 'S', "", "" );
#endif
#ifdef TRACE
if (context_sw_trace && (action != DONE_CREATION)) {
/* Don't print internal CS during creation */
/* i.e. task x switching to itself. */
printf("task %d switching to task %d \n",
tp,item->tcbs[i]);
}
#endif
highest_priority = p;
transfer(item->tcbs[i]);
done = TRUE;
break;
case CREATE :
create_task(RTS_TEMPL_BASE(item),
RTS_TEMPL_OFF(item),item,i);
break;
default :
raise(SYSTEM_ERROR, "Unexpected type");
break;
}
break; /* out of for loop */
}
else if (p == 0) done = TRUE; /* end loop (error) */
}
}
if (item == (struct rts_item *)0)
{ /* No active task was found, error condition */
MY_STATUS = ACTIVE;
raise(SYSTEM_ERROR, "No activatable task");
MY_EXCEPTION = 0;
return;
}
finish_action(item);
}
/* Now the new task is processing. It must perform the appropriate */
/* cleanup routine based on the action which it was performing before */
/* making the call to the scheduler ... */
static void finish_action(struct rts_item *item) /*;finish_action*/
{
int open_en[MAX_ALTS], accept_index[MAX_ALTS];
long sleep_time;
long itime();
switch (MY_ACTION) {
case WAIT :
post_wait();
break;
case SELECTIVE_WAIT :
post_selective_wait(0, 0, open_en, accept_index);
break;
case ENTRY_CALL :
post_entry_call();
break;
case COMPLETE_BLOCK_ONE :
post_complete_block();
break;
case COMPLETE_BLOCK_TWO :
post_complete_block_two();
break;
case ABORT_ONE :
post_abort_one();
break;
case COMPLETE_TASK_ONE :
post_complete_task_one();
break;
case FREE_TASK_SPACE :
free_task_space();
break;
case DONE_ACTIVATION :
done_activation();
break;
case DONE_CREATION :
done_creation();
break;
case ACTIVATE_SELF :
activate_self(RTS_MASTER_TASK(item), RTS_MASTER_BLOCK(item));
break;
case CONTEXT_SWITCH :
case NO_ACTION :
break;
default :
raise(SYSTEM_ERROR, "Tasks awakened in an unknown state");
MY_EXCEPTION = 0;
break;
}
/* then see if someone else waiting to be scheduled */
if (tp == 0) { /* we schedule IDLE */
if (next_clock_flag) { /* exists task waiting? */
if (simul_time_flag) /* speed up time! */
time_offset += next_clock - itime();
else if ((sleep_time=(next_clock-itime())/1000L) > 1L)
sleep((unsigned)sleep_time);
}
else if ((MY_ACTION != DONE_CREATION) && (MY_ACTION != DONE_ACTIVATION))
raise(PROGRAM_ERROR, "System inactive(deadlock)");
}
MY_ACTION = NO_ACTION;
}
/*--------------------------------------------------------------------------*/
/* MAKE READY */
/* Moves the specified task to the ready queue setting it's event flag. */
/*--------------------------------------------------------------------------*/
static void make_ready(int task, int event) /*;make_ready*/
{
if (event != NO_EVENT) /* Mulitple events may overwrite (COMP,TERM,ABORT) */
TCB_EVENT(task)=event; /* but task can tell if correct one happened */
if (INC(TCB_NUM_EVENTS(task)) == -1) enqueue(TCB_PRIO(task), task);
}
/*--------------------------------------------------------------------------*/
/* ROUND ROBIN */
/* */
/* Procedure called when the round-robin scheme is invoked. First, detect */
/* (asynchronously !) if there is a ready task of higher or same priority */
/* than the task blocked. If yes, perform the context_switch. */
/* WARNING: the task blocked by the round-robin scheme has still the */
/* status ACTIVE !!! */
/* */
/*--------------------------------------------------------------------------*/
void round_robin() /*;round_robin*/
{
int p, found;
/* is it worth doing a context-switch ? */
/* --> test if there is one ready task of higher or same priority */
/* warning: asynch test (Cf dequeue with DEC & INC) */
found = FALSE;
for (p=MAX_PRIO-1; p>=MY_PRIO; p--)
if (ready_queue[p]->count >= 1)
found = TRUE;
if (found)
context_switch();
}
/*---------------------------------------------------------------------------*/
/* TRANSFER */
/* Transfers control from the currently executing task to the one specified */
/*---------------------------------------------------------------------------*/
static void transfer(int new_task) /*;transfer*/
{
PUSH(exr);
PUSH(lin);
PUSH(sfp);
PUSH(cs);
PUSH(ip);
PUSH(bfp); /* ----------MUST be on top of stack---------- */
MY_BLOCK_PTR = (int *) (cur_stack+cur_stackptr);
STACKPTR(tp) = cur_stackptr;
tp = new_task;
rr_counter = 0;
cur_stack = STACK(tp);
cur_stackptr = STACKPTR(tp);
POP(bfp);
MY_BLOCK_PTR = &bfp;
POP(ip);
POP(cs);
POP(sfp);
POP(lin);
POP(exr);
cur_code = code_segments[cs];
}
/*--------------------------------------------------------------------------*
* R E N D E Z V O U S *
* *
* The following set of procedures are used to performs rendezvous. They *
* are divided into two sets: those used to perform a "select" call and *
* those used to execute an "entry" call. They make use of the parallel *
* queue routines and use the "rdv" flag, the array of "entry" records, the *
* "io_item" when processing delays as well as checking and setting the *
* "status" and "abnormal" fields in the caller and/or owner task's TCB. *
* The routines which are called by the interpretor directly are: *
* - selective_wait *
* - end_rendezvous *
* - entry_call *
* There calling interface (stack upon entry and return as well as parms. *
* passed are identical to the original system. *
*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*
* SELECTIVE WAIT *
*--------------------------------------------------------------------------*/
void selective_wait(int num_alts) /*;selective_wait */
{
int alt_kind; /* delay, terminate or accept */
long delay_time; /* value of delay */
long min_delay; /* minimum delay */
int family; /* family index of entry */
int member; /* member of family */
int guard; /* guard of statement (boolean) */
int open_ai[MAX_ALTS]; /* list of open accept indices */
int open_en[MAX_ALTS]; /* list of open entry numbers */
int num_open_alts; /* number of open alternatives */
int delay_index; /* index to delay alt. (or -1) */
int term_index; /* index to terminate alt. (or -1) */
int accept_index; /* index to chosen alternative */
int caller; /* chosen task id of caller */
struct entry_type *entry; /* pointer to entry */
int i, temp;
/* STEP 1: Build a set of open alternatives and determine the shortest
* delay and the index set of the terminate (if any)
*/
term_index = -1;
min_delay = ENDLESS;
delay_index = -1;
if (num_alts == 0) { /* simple accept */
POP(member);
POP(family);
num_open_alts = 1;
open_ai[0] = 1;
open_en[0] = entry_number(tp, family, member);
}
else num_open_alts = 0;
for (accept_index = num_alts; accept_index >= 1; accept_index --) {
POP(alt_kind);
if (alt_kind == 1) { /* accept */
POP(member);
POP(family);
POP(guard);
if (guard == 1) {
open_ai[num_open_alts] = accept_index;
open_en[num_open_alts++] = entry_number(tp, family, member);
}
}
else if (alt_kind == 2) { /* delay */
POPL(delay_time);
POP(guard);
if (guard == 1)
if (delay_index == -1 || delay_time<min_delay) {
min_delay = (delay_time>=0L) ? delay_time : 0L;
delay_index = accept_index;
}
}
else if (alt_kind == 3) { /* terminate */
POP(guard);
if (guard == 1)
term_index = accept_index;
}
else raise(SYSTEM_ERROR,
"Unknown alternative in select statement");
}
/* STEP 2: Check local status, terminatability, guards, rdv flag etc. */
if (MY_ABNORMAL) {
abortme();
return;
}
MY_STATUS = SELECTING_NOTERM;
evaluate_guards(open_en, num_open_alts);
FAS(&(MY_RDV), 1); /* an rdv is now possible */
if (MY_ABNORMAL && (FAS(&(MY_RDV), 0) == 1)) {
abortme();
return;
}
/* STEP 3: See if can start an immediate rendezvous */
if (num_open_alts > 0) {
i = 0;
while (MY_RDV && (i < num_open_alts)) {
temp = open_ai[i];
entry = MY_ENTRY(open_en[i++]);
if (ENTRY_GUARD(entry)) {
del_lock(&(LOCK(entry)));
if ((DEC(ENTRY_COUNT(entry)) > 0) && (FAS(&(MY_RDV),0) == 1)) {
/* a task is waiting and got flag so rdv is possible */
caller = eget(entry);
del_unlock(&(LOCK(entry)));
close_guards(open_en, num_open_alts);
TCB_SAVE_PRIO(caller) = MY_PRIO;
MY_PRIO = MAX(TCB_SAVE_PRIO(caller),MY_PRIO);
accept_rdv(caller,temp,num_alts);
return;
}
else { /* we did not - disable the rendezvous... */
INC(ENTRY_COUNT(entry));
del_unlock(&(LOCK(entry)));
}
}
}
}
/* STEP 4: Unfortunately, couldn't perform rendezvous. Now either detect
* an error condition, try to terminate or delay
*/
if ((num_open_alts == 0) && (delay_index == -1) && (term_index == -1)){
raise(PROGRAM_ERROR, "No open alternatives in select");
return;
}
if (MY_RDV == 0) { /* have been aborted or called, busywait */
while ((MY_EVENT != RDV_EVENT) && (!MY_ABNORMAL)) continue;
post_selective_wait(1, num_open_alts, open_en, open_ai);
}
else if ((min_delay == 0) && FAS(&(MY_RDV), 0) == 1) {
MY_EVENT = TIMER_EVENT;
open_en[num_open_alts] = TIMER_EVENT;
open_ai[num_open_alts++] = delay_index;
post_selective_wait(1, num_open_alts, open_en, open_ai);
return;
}
else {
if (num_alts != 0) /* push open entry table on stack */
for (i=0;i<num_open_alts;i++) {
PUSH(open_en[i]);
PUSH(open_ai[i]);
}
if (delay_index != -1) {
PUSH(TIMER_EVENT);
PUSH(delay_index);
num_open_alts++;
}
if (term_index != -1) {
PUSH(TERMINATE_EVENT);
PUSH(term_index);
MY_STATUS = SELECTING_TERM;
if (DEC(MY_NUM_NOTERM) == 1) /* am quiescent */
check_termination(MY_MASTER_TASK, MY_MASTER_BLOCK);
num_open_alts++;
}
if (num_alts == 0) /* simple accept */
PUSH(0);
else
PUSH(num_open_alts);
#ifdef GWUMON
CWK_CHANGE_TASK_STAT(tp, 'R', "", "" );
#endif
#ifdef TRACE
if (rendezvous_trace)
printf("task %d waiting for a rendezvous \n", tp);
#endif
if (min_delay == ENDLESS) schedule(SELECTIVE_WAIT);
else wait(min_delay, SELECTIVE_WAIT);
}
}
/*--------------------------------------------------------------------------*/
/* EVALUATE GUARDS */
/*--------------------------------------------------------------------------*/
static void evaluate_guards(int open_en[], int num_open) /*;evaluate_guards*/
{
int i;
struct entry_type *entry;
for (i=0;i< num_open;i++) {
entry = MY_ENTRY(open_en[i]);
del_lock(&(LOCK(entry)));
ENTRY_GUARD(entry) = 1;
del_unlock(&(LOCK(entry)));
}
}
/*--------------------------------------------------------------------------*/
/* CLOSE GUARDS */
/*--------------------------------------------------------------------------*/
static void close_guards(int open_en[], int num_open) /*;close_guards*/
{
int i;
struct entry_type *entry;
for (i=0;i< num_open;i++) {
if ((open_en[i] != TERMINATE_EVENT) && (open_en[i] != TIMER_EVENT)){
entry = MY_ENTRY(open_en[i]);
del_lock(&(LOCK(entry)));
ENTRY_GUARD(entry) = 0;
del_unlock(&(LOCK(entry)));
}
}
}
/*--------------------------------------------------------------------------*/
/* POST SELECTIVE WAIT */
/*--------------------------------------------------------------------------*/
static void post_selective_wait(int in_mem, int num_alts, int open_en[],
int open_ai[]) /*;post_selective_wait*/
{
int status, x, ai, accept_index;
status = MY_STATUS;
MY_STATUS = ACTIVE;
if (MY_ABNORMAL) {
if (status == SELECTING_TERM) MY_STATUS = QUIESCENT;
abortme();
return;
}
if (status == SELECTING_TERM) check_unterm();
/* We must find the accept index for the selected alternative ... */
if (MY_EVENT == TIMER_EVENT) MY_WHAT = TIMER_EVENT;
accept_index = -1;
if (in_mem) {
if (num_alts != 0)
for (x = 0; x < num_alts; x++)
if (open_en[x] == MY_WHAT) accept_index=open_ai[x];
}
else {
POP(num_alts);
if (num_alts != 0)
for (x =0 ; x < num_alts; x++) {
POP(ai);
POP(open_en[x]);
if (open_en[x] == MY_WHAT) accept_index = ai;
}
}
if ((num_alts != 0) && (accept_index == -1)) {
raise(SYSTEM_ERROR, "Nonexistant alternative selected");
return;
}
close_guards(open_en, num_alts);
switch (MY_EVENT) {
case TIMER_EVENT :
if (status == WAIT) {
efree((char *) MY_IO_ITEM);
MY_IO_ITEM = NULL;
}
PUSH(accept_index);
break;
case RDV_EVENT :
disable_io(&MY_IO_ITEM);
accept_rdv(MY_WHO,accept_index,num_alts);
break;
default :
raise(SYSTEM_ERROR,
"Unexpected event in select/accept");
break;
}
}
/*--------------------------------------------------------------------------*/
/* ACCEPT */
/*--------------------------------------------------------------------------*/
static void accept_rdv(int caller, int accept_index, int push_index)
/*;accpt_rdv*/
{
int num_param; /* number of parameters to transfer */
int disp, i;
/* Must copy parameters over from the caller's stack */
disp = STACKPTR(caller) - NB_REGISTERS;
num_param = STACK(caller)[disp];
disp -= num_param + 1;
for (i = 1; i <= num_param; i++)
PUSH(STACK(caller)[disp+i]);
add_serviced(tp, caller); /* may be nested rdv need if aborted */
if (push_index)
PUSH(accept_index);
}
/*--------------------------------------------------------------------------*/
/* END_RENDEZVOUS */
/*--------------------------------------------------------------------------*/
void end_rendezvous() /*;end_rendevous*/
{
int caller;
caller = remove_serviced();
#ifdef GWUMON
CWK_CHANGE_TASK_STAT(caller, 'M', "", "" );
#endif
#ifdef TRACE
if (rendezvous_trace)
printf("task %d end rendezvous with task %d \n",tp, caller);
#endif
MY_PRIO = TCB_SAVE_PRIO(caller);
if (MY_EXCEPTION) /* propagate exception to calling task */
TCB_EXCEPTION(caller) = MY_EXCEPTION;
make_ready(caller, ENDRDV_EVENT);
if (MY_ABNORMAL) { /* owner aborted, raise TASKING ERROR */
TCB_EXCEPTION(caller) = TASKING_ERROR;
abortme();
return;
}
}
/*--------------------------------------------------------------------------*/
/* ENTRY CALL */
/*--------------------------------------------------------------------------*/
void entry_call(long delay_time, int num_param) /*;entry_call*/
{
struct entry_type *entry;
int owner; /* owner of entry */
int family; /* index of family of the entry */
int member; /* index of member of the entry */
int entry_num; /* actual entry number */
/* STEP 1: Get calling information from the stack */
member = TOSM(num_param);
family = TOSM(num_param + 1);
owner = TOSM(num_param + 2);
if (is_callable(owner) == FALSE) {
switch(TCB_STATUS(owner)) {
case TERMINATED:
raise(TASKING_ERROR, "Call to an entry of a terminated task");
return;
case COMPLETED:
default:
raise(TASKING_ERROR, "Call to an entry of a completed task");
return;
}
}
entry_num = entry_number(owner, family, member);
#ifdef GWUMON
CWK_CHANGE_TASK_STAT(tp, 'R', "", "" );
#endif
#ifdef TRACE
if (rendezvous_trace)
printf("task %d calling rendezvous with task %d entry %d\n",
tp, owner, entry_num);
#endif
/* STEP 2: Perform some error detection */
if (entry_num > TCB_NUM_ENTRIES(owner)) {
raise(SYSTEM_ERROR, "Nonexistant entry called");
return;
}
/* STEP 3: Set statuses */
if (MY_ABNORMAL) {
abortme();
return;
}
if (delay_time == ENDLESS)
MY_STATUS = CALLING_RDV;
else MY_STATUS = TIMED_RDV;
entry = TCB_ENTRY(owner, entry_num);
PUSH(num_param);
add_lock(&(LOCK(entry)));
/* STEP 4: See if immediate rendezvous */
if ((INC(ENTRY_COUNT(entry)) == 0) && ENTRY_GUARD(entry) &&
(FAS(&(TCB_RDV(owner)), 0) == 1)) {
/* owner is commited to a rendezvous with the caller */
DEC(ENTRY_COUNT(entry)); /* restore counter */
TCB_WHAT(owner) = entry_num;
TCB_WHO(owner) = tp;
add_unlock(&(LOCK(entry)));
disable_io(&(TCB_IO_ITEM(owner)));
MY_SAVE_PRIO = TCB_PRIO(owner);
TCB_PRIO(owner) = MAX(TCB_PRIO(owner),MY_PRIO);
make_ready(owner, RDV_EVENT);
schedule(ENTRY_CALL); /* wait for end of rdv */
}
/* STEP 5: Cannot rendezvous immediately */
else { /* cannot immediately start a rendezvous */
if (delay_time == 0) { /* else changed to "delay 0" */
DEC(ENTRY_COUNT(entry));
add_unlock(&(LOCK(entry)));
cur_stackptr -= 3;
PUSH(0);
}
else {
MY_CURR_ENTRY = entry;
eput(entry, tp, &MY_ENTRY_ITEM);
add_unlock(&(LOCK(entry)));
if (MY_ABNORMAL && eremove(tp)) {
abortme();
return;
}
if (delay_time != ENDLESS)
MY_IO_ITEM = chain(tp, delay_time);
schedule(ENTRY_CALL);
}
}
}
/*-------------------------------------------------------------------*/
/* POST ENTRY CALL */
/*-------------------------------------------------------------------*/
static void post_entry_call() /*;post_entry_call*/
{
int num_param; /* number of parameters */
int i;
POP(num_param);
if (MY_ABNORMAL) {
disable_io(&MY_IO_ITEM);
abortme();
return;
}
i = MY_EVENT;
switch (MY_EVENT) {
case TIMER_EVENT :
efree((char *) MY_IO_ITEM);
MY_IO_ITEM = NULL;
cur_stackptr -= 3;
PUSH(0);
break;
case ENDRDV_EVENT:
disable_io(&MY_IO_ITEM);
if (MY_EXCEPTION)
raise(MY_EXCEPTION, "Exception propagated from called task");
MY_EXCEPTION = 0;
for (i = cur_stackptr - num_param - 3 + 1; i <= cur_stackptr; i++)
cur_stack[i] = cur_stack[i+3];
cur_stackptr -=3;
if (MY_STATUS == TIMED_RDV)
PUSH(1);
break;
case TASKERR_EVENT :
raise(TASKING_ERROR, "Entry call failed: called task terminated");
MY_EXCEPTION = 0;
break;
default :
raise(SYSTEM_ERROR, "Unexpected event in entry call");
break;
}
MY_STATUS = ACTIVE;
}
/*-----------------------------------------------------------------------*/
/* U T I L I T Y R O U T I N E S */
/*-----------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
/* ENTRY QUEUE ROUTINES */
/* The following four routines (einit, eremove, eput & eget) are parallel */
/* access queue routines for maintaining the entry queue in tasks' tcbs. */
/*--------------------------------------------------------------------------*/
static void einit(struct entry_type *entry) /*;einit*/
{
ENTRY_LAST(entry) = NULL;
ENTRY_FIRST(entry) = NULL;
(LOCK(entry)).add_lock = 0;
(LOCK(entry)).del_lock = 0;
ENTRY_GUARD(entry) = 0;
ENTRY_COUNT(entry) = 0;
}
static int eremove(int task) /*;eremove*/
{
if ((TCB_ENTRY_ITEM(task) == NULL) || (TCB_CURR_ENTRY(task) == NULL))
return FALSE;
add_lock(&(LOCK(TCB_CURR_ENTRY(task))));
if (FAS(&(ITEM_FLAG(TCB_ENTRY_ITEM(task))), 0) == 0) {
add_unlock(&(LOCK(TCB_CURR_ENTRY(task))));
return FALSE;
}
else {
DEC(ENTRY_COUNT(TCB_CURR_ENTRY(task)));
add_unlock(&(LOCK(TCB_CURR_ENTRY(task))));
TCB_CURR_ENTRY(task) = NULL;
TCB_ENTRY_ITEM(task) = NULL;
return TRUE;
}
}
static void eput(struct entry_type *entry, int caller, struct q_item **ret_item)
/*;eput*/
{
struct q_item *prev_item, *item;
item = (struct q_item*) malloc(sizeof(struct q_item));
if (item == (struct q_item*)0) {
raise(STORAGE_ERROR, "Allocating space for entry queue");
return;
}
ITEM_FLAG(item) = 1;
ITEM_TASK(item) = caller;
ITEM_NEXT(item) = NULL;
prev_item = FAS_Q(&(ENTRY_LAST(entry)), item);
if (prev_item == (struct q_item *)0)
FAS_Q(&(ENTRY_FIRST(entry)), item);
else
FAS_Q(&(ITEM_NEXT(prev_item)), item);
*ret_item = item;
}
static int eget(struct entry_type *entry) /*;eget*/
{
struct q_item *caller, *new_caller;
int task;
del_lock(&(LOCK(entry)));
if (ENTRY_FIRST(entry) == NULL) {
del_unlock(&(LOCK(entry)));
return NULL_TASK;
}
ENTRY_GUARD(entry) = 0;
caller = FAS_Q(&(ENTRY_FIRST(entry)), (ITEM_NEXT(ENTRY_FIRST(entry))));
while ((caller != (struct q_item*)0) && (FAS(&(ITEM_FLAG(caller)),0) == 0)){
new_caller = FAS_Q(&(ENTRY_FIRST(entry)),ITEM_NEXT(ENTRY_FIRST(entry)));
efree((char *) caller);
caller = new_caller;
}
if (caller != (struct q_item*)0) {
if (TCB_IO_ITEM(ITEM_TASK(caller)) != NULL)
disable_io(&(TCB_IO_ITEM(ITEM_TASK(caller))));
if (ENTRY_COUNT(entry) == 0) FAS_Q(&(ENTRY_LAST(entry)), NULL);
task = ITEM_TASK(caller);
efree((char *) caller);
TCB_ENTRY_ITEM(task) = (struct q_item *)0;
}
else task = NULL_TASK;
del_unlock(&(LOCK(entry)));
return(task);
}
/*---------------------------------------------------------------------------*
* LOCKING ROUTINES *
* *
* Locks on entries may be either "add" locks or "del" locks. "del" (or *
* delete) locks have precedence over "add" locks. These are implemented *
* using a standard A-B locking scheme. *
*---------------------------------------------------------------------------*/
static void add_lock(struct lock_type *lock) /*;add_lock*/
{
for (;;) {
while (lock->del_lock > 1) continue; /* wait for dels to end */
INC(lock->add_lock); /* try tp get add lock */
if (lock->del_lock > 1) /* oops, del started... */
DEC(lock->add_lock);
else break;
}
}
static void add_unlock(struct lock_type *lock) /*;add_unlock*/
{
DEC(lock->add_lock);
}
static void del_lock(struct lock_type *lock) /*;del_lock */
{
INC(lock->del_lock); /* don't allow adds to start */
while (lock->add_lock > 1) continue; /* wait for adds to end */
}
static void del_unlock(struct lock_type *lock) /*;del_unlock*/
{
DEC(lock->del_lock);
}
/*-----------------------------------------------------------------------*/
/* ENTRY NUMBER */
/*-----------------------------------------------------------------------*/
static int entry_number(int owner, int family, int member) /*;entry_number*/
{
return (*ADDR(TCB_TBASE(owner), TCB_TOFF(owner) + 2 * (family -1))
+ member + 1);
}
/*------------------------------------------------------------------------*
* RAISE IN CALLER *
* Procedure to raise an exception in the task waiting for the completion *
* of the current rendezvous *
* -----------------------------------------------------------------------*/
void raise_in_caller() /*;raise_in_caller*/
{
#ifdef GWUMON
CWK_CHANGE_TASK_STAT(tp, 'B', "", "" );
{
char msg[240];
sprintf(msg, "task %d raising exception %s in task %d\n",tp,
exception_slots[exr],MY_SERVICED);
CWK_Exception_Raised( tp, msg );
}
#endif
#ifdef TRACE
if (exception_trace)
printf("task %d raising exception %s in task %d\n",tp,
exception_slots[exr],MY_SERVICED);
#endif
TCB_EXCEPTION(MY_SERVICED) = exr;
}
/* -----------------------------------------------------------------------*
* ATTRIBUTE ROUTINES *
* The following three routines (is_callable, is_terminated and count) *
* return the attributes 'CALLABLE, 'TERMINATED and 'COUNT respecitvely *
* for the given task *
* -----------------------------------------------------------------------*/
int is_callable(int task) /*;is_callable*/
{
if (task != ORIG(task) || STACK(task) == (int *)0 || TCB_ABNORMAL(task))
return FALSE;
switch(TCB_STATUS(task)) {
case TERMINATED:
case ABNORMAL:
return FALSE;
case COMPLETED: /* are we completed on the last level? */
return BF_PREVIOUS_BFP(task, *(TCB_BLOCK_PTR(task))) == NULL_TASK;
default:
return TRUE;
}
}
int is_terminated(int task) /*;is_terminated*/
{
if (task != ORIG(task) || STACK(task) == (int *)0 ) return TRUE;
return (TCB_STATUS(task) == TERMINATED);
}
int count(int family, int member) /*;count*/
{
return(ENTRY_COUNT(MY_ENTRY(entry_number(tp, family, member))));
}
/*---------------------------------------------------------------------------*/
/* MULTISIGNAL */
/*---------------------------------------------------------------------------*/
static void multisignal(int task, int event) /*;multisignal*/
{
if (event != NO_EVENT) TCB_EVENT(task) = event;
if (DEC(TCB_NUM_ITEMS(task)) == 1)
make_ready(task, NO_EVENT);
}
/*---------------------------------------------------------------------------*/
/* "SERVICED" QUEUE */
/*---------------------------------------------------------------------------*/
static void add_serviced(int server, int task) /*;add_serviced*/
{
TCB_NEXT(task) = FAS(&(TCB_SERVICED(server)), task);
}
static int remove_serviced() /*;remove_serviced*/
{
return(FAS(&(MY_SERVICED), TCB_NEXT(MY_SERVICED)));
}
/*--------------------------------------------------------------------------*/
/* READY QUEUE */
/* The following three routines (qinit, enqueue, enqueue_item and dequeue) */
/* are the routines used in accessing the ready queue. The other three */
/* routines are used to control the scheduler. */
/*--------------------------------------------------------------------------*/
static void qinit() /*;qinit*/
{
int i;
struct ready_q *q;
for (i = 0; i < MAX_PRIO; i++) {
ready_queue[i] = (struct ready_q *)malloc(sizeof(struct ready_q));
q = ready_queue[i];
if (q == (struct ready_q *)0) {
printf("Unable to allocate stack\n");
exit(RC_ABORT);
}
q->first = NULL;
q->last = NULL;
q->first_mult = 0;
q->count = 0;
q->lock.add_lock = 0;
q->lock.del_lock = 0;
}
}
static void enqueue(int priority, int value) /*;enqueue*/
{
struct rts_item *item;
item = (struct rts_item *) malloc(sizeof(struct rts_item));
if (item == (struct rts_item *)0){
raise(STORAGE_ERROR, "Allocating space for ready queue");
return;
}
item->tcbs = (int *) malloc(sizeof(int));
if (item->tcbs == (int *)0){
raise(STORAGE_ERROR, "Allocating space for ready queue");
return;
}
RTS_TYPE(item) = READY;
RTS_PRIO(item) = priority;
RTS_MULT(item) = 1;
RTS_NEXT(item) = NULL;
RTS_TCBS(item, 0) = value;
enqueue_item(priority, item);
}
static void enqueue_item(int priority, struct rts_item *item) /*;enqueue_item*/
{
struct ready_q *q;
struct rts_item *prev;
RTS_SAVE_MULT(item) = RTS_MULT(item);
q = ready_queue[priority];
while (highest_priority < priority)
priority = FAS(&highest_priority, priority);
add_lock(&(LOCK(q)));
prev = FAS_RTS(&(q->last), item);
if (prev == (struct rts_item *)0) {
FAS_RTS(&(q->first), item);
FAS(&(q->first_mult), item->mult);
}
else FAS_RTS(&(prev->next), item);
q->count += item->mult;
add_unlock(&(LOCK(q)));
}
static struct rts_item *dequeue(struct ready_q *q, int *i) /*;dequeue*/
{
struct rts_item *item;
int j, k;
if (q->count <= 0) return (struct rts_item *)0;
else if (DEC(q->count) < 1) {
INC(q->count);
return (struct rts_item *)0;
}
else {
while (q->first_mult > 0 && DEC(q->first_mult) < 1) continue;
item = q->first;
if ((j = DEC(q->first->mult)) == 1) { /* remove from list */
RTS_MULT(q->first) = RTS_SAVE_MULT(q->first);
if (q->first->next == NULL) {
del_lock(&(LOCK(q)));
if (q->first->next == NULL) {
FAS_RTS(&q->first, NULL);
FAS_RTS(&q->last, NULL);
}
else {
FAS_RTS(&(q->first), q->first->next);
FAS(&(q->first_mult), q->first->mult);
}
del_unlock(&(LOCK(q)));
}
else {
FAS_RTS(&(q->first), q->first->next);
FAS(&(q->first_mult), q->first->mult);
}
}
k = 0;
*i = -1;
while (k < j)
if (item->tcbs[++(*i)] != NULL_TASK) k++;
return item;
}
}
static void context_switch() /*;context_switch*/
{
DEC(MY_NUM_EVENTS);
make_ready(tp, TIMER_EVENT);
schedule(CONTEXT_SWITCH);
}
#ifdef IBM_PC
static void sleep(unsigned secs)
{
#ifdef GWUMON
if ( type_of_delay == 1 )
{
long check = itime();
check += secs * CLOCKS_PER_SEC;
while ( itime() < check )
{
if ( step_rate == 0 )
busy_wait(1);
else
busy_wait(step_rate);
CWK_TIME_TASK();
}
}
else
{
clock_t check = clock();
check += secs * CLOCKS_PER_SEC;
while (clock() < check)
;
}
#else
clock_t check = clock();
check += secs * CLOCKS_PER_SEC;
while (clock() < check)
;
#endif
}
#endif